Skip to content

Add LinkedIn-style profile picture cropper (zoom + pan)#140

Merged
vincentmakes merged 2 commits into
mainfrom
claude/profile-picture-adjustment-oVNVU
Apr 21, 2026
Merged

Add LinkedIn-style profile picture cropper (zoom + pan)#140
vincentmakes merged 2 commits into
mainfrom
claude/profile-picture-adjustment-oVNVU

Conversation

@vincentmakes

Copy link
Copy Markdown
Owner

Description

Adds a LinkedIn-style profile picture cropper modal that allows users to zoom and pan their profile picture before saving. Users can adjust existing pictures or newly uploaded ones with a visual cropper interface featuring:

  • Zoom control: Slider to scale the image (1x–4x)
  • Pan support: Drag to reposition the image within the circular crop area
  • Two modes: Adjust newly uploaded pictures or existing saved pictures
  • Crop persistence: Crop metadata (offsetX, offsetY, zoom) is stored per-profile and restored when loading dataset snapshots
  • Propagation options: Crop can be applied to all datasets or just language siblings (matching picture-propagate behavior)

The implementation uses Cropper.js (CDN-loaded) for the interactive UI and stores normalized crop values in a new picture_crop database column.

Type of Change

  • New feature (non-breaking change that adds functionality)

Checklist

Required for all code changes

  • I have tested my changes locally (npm test passes)
  • Version has been bumped in all 3 files (package.json, package-lock.json, version.json)
  • CHANGELOG.md has been updated with a new entry under the correct version

If adding or changing user-visible strings

  • No hardcoded English — all strings use t('key') in JS or data-i18n in HTML
  • New i18n keys added to en.json and all 7 other locale files (de, fr, nl, es, it, pt, zh)
  • escapeHtml() used for any user-provided content rendered as HTML

Key Changes

Backend (src/server.js)

  • Added picture_crop TEXT column to profile table with migration
  • New normalizeCrop() function to validate and clamp crop values (offsetX/Y: ±100, zoom: 1–4)
  • New endpoints:
    • PUT /api/profile/picture/crop — save crop metadata with propagation support
    • Updated GET /api/profile to include picture_crop field
  • Crop is reset to NULL when a new picture is uploaded/selected
  • Crop propagates to all datasets (if applyToAll: true) or language siblings only (if false)
  • Dataset load restores the snapshot's crop onto the live profile

Frontend (public/shared/admin.js)

  • New cropper modal with Cropper.js integration
  • openCropperForNewUpload() — auto-opens cropper after file selection (LinkedIn parity)
  • openCropperForExisting() — opens cropper for the current saved picture
  • readCropFromCropper() / cropToCropperData() — pure math helpers for crop ↔ Cropper.js data conversion
  • Crop is queued in pendingProfilePictureCrop and applied server-side after file save
  • "Adjust Picture" button added to profile form (disabled when no picture exists)

Frontend (public/shared/scripts.js)

  • New applyProfilePictureCrop() helper to render crop via CSS (object-position + transform: scale())
  • Crop is applied when loading profile pictures

UI & Styling (public/index.html, public/shared/admin.css)

  • Cropper.js CDN links (CSS + JS)
  • Modal markup with zoom slider, drag hint, and action buttons
  • Circular crop overlay (matches 110px profile circle)
  • Prefix .pp-crop- to avoid collision with Cropper.js's internal .cropper-modal class

Internationalization

  • Added 9 new i18n keys across all 8 locales:
    • btn.reset_crop, form.adjust_picture, form.adjust_hint, form.zoom, modal.adjust_picture, toast.crop_saved, toast.no_picture

Tests

https://claude.ai/code/session_01WcdrYPM5U9jMgcjMUmRsFg

claude added 2 commits April 21, 2026 19:50
Uploading or re-opening a profile picture now opens a circular cropper
(Cropper.js via CDN) with drag-to-reposition and a zoom slider. The
original upload stays untouched on disk; only normalized crop metadata
is stored in a new profile.picture_crop column and applied on render
via CSS object-position + transform: scale(). A new "Adjust" button in
the profile modal lets users re-frame the saved picture at any time
without re-uploading.

New PUT /api/profile/picture/crop endpoint (clamps values server-side);
uploading/selecting/removing a picture resets the crop. Crop
propagation mirrors the existing picture_propagate semantics: ON
writes into every dataset, OFF into language siblings only. Seven new
i18n keys translated across all 8 locales.

Bump to 1.47.0.
The outer dialog was named `.cropper-modal`, which is also the class
Cropper.js uses for its internal dark overlay outside the crop box.
Our `position: fixed; inset: 0;` and semi-transparent background were
being applied to Cropper.js's internal element too, stretching it
across the viewport and letting the form behind bleed through the
dialog.

Renamed the outer wrapper classes to `.pp-crop-modal*` so Cropper.js
keeps its intended sizing and our dialog chrome stays opaque.

Bump to 1.47.1.
@vincentmakes vincentmakes merged commit 90b99ed into main Apr 21, 2026
3 checks passed
@vincentmakes vincentmakes deleted the claude/profile-picture-adjustment-oVNVU branch April 21, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants